' blitzmaxplugin.bmx

Strict

Import pub.FreeProcess

Import "../maxtk/maxtk.bmx"
'Import axe.MaxEdit

Global is_demo

Global bmxpath$,bmkpath$

Type TBMXOptions Extends TOptionsPage

	Field	owner:TBLitzMaxPlugin
	Field	blitzmaxpath:TPathButton
	Field	syntaxhighlight:TButton
	Field	capitalize:TButton
	Field	autoindent:TButton
	Field	tabsize:TNumberButton
	Field	editcolor:TColorButton
	Field	editfont:TFontButton
'	Field	editstyle:TTextStyle
	Field	styles:TTextStyle[4]
		
	Method Dirty(cause:TDataNode)	'called by editor.ReadConfig
		Local bmxpath$=blitzmaxpath.ToString()
		If owner.SetBMXPath(bmxpath)
			If cause=blitzmaxpath ImportMaxPrefs
		EndIf
		Super.Dirty cause
	End Method

	Method ResetPath()
		Local bmxpath$
		bmxpath=blitzmaxpath.ToString()
		If bmxpath="Not Found"
			bmxpath=FindBMXPath(AppDir$)
			If bmxpath 
				If owner.SetBMXPath(bmxpath)
					blitzmaxpath.Set(bmxpath)
					ImportMaxPrefs
				EndIf
			EndIf
		EndIf
	End Method
	
	Method CreateBMXOptions:TBMXOptions(plugin:TBlitzMaxPlugin)
		SetName "BlitzMax Options"
		owner=plugin
		plugin.editor.hostoptions.AddPage Self
		blitzmaxpath=AddPathButton("BlitzMax Path","")
		syntaxhighlight=AddButton("Syntax Highlight",2)
		capitalize=AddButton("Capitalize Keywords",2)
		autoindent=AddButton("Automatic Indent",2)
		tabsize=AddNumberButton("Tab Size",4)
		editcolor=AddColorButton("Background",-1)
		editfont=AddFontButton("Font","Courier",12,0)
'		editstyle=AddTextStyle(Self,"Style")'ComboButton("Style",["Normal","Bold","Italic","Bold+Italic"])
		styles=New TTextStyle[4]
		styles[0]=AddTextStyle(Self,"Default")
		styles[1]=AddTextStyle(Self,"Remarks")
		styles[2]=AddTextStyle(Self,"Strings")
		styles[3]=AddTextStyle(Self,"Tokens")
		Layout
		Return Self
	End Method

	Function FindBMXPath$(path$)
		If FileType(path+"/bin") Return path
		path=RealPath(".")
		If FileType(path+"/bin") Return path
		path=RealPath("..")
		If FileType(path+"/bin") Return path
		path=RealPath("../..")
		If FileType(path+"/bin") Return path
		path=RealPath("../../..")
		If FileType(path+"/bin") Return path
	End Function
	
	Function ImportStyle(style:TTextStyle,s$)	
		Local	p
		p=s.FindLast(",")
		style.color.FromString s[..p]
		style.combo.set Int(s[p+1..])
	End Function

	Method ImportMaxPrefs()
		Local stream:TStream
		Local f$,p,a$,b$,t
		Local editfontname$,editfontsize
		
		If Proceed("Import MaxIDE Settings?")=False Return	
		stream=ReadFile(bmxpath+"/cfg/ide.ini")
		If Not stream Notify("Could not open MaxIDE prefs from~n"+bmxpath+"/cfg/ide.ini");Return
		While Not stream.Eof()
			f$=stream.ReadLine()
			p=f.find("=")
			a$=f[..p]
			b$=f[p+1..]
			t=Int(b)
			Select a$
				Case "editfontname" editfontname=b
				Case "editfontsize" editfontsize=t
				Case "syntaxhighlight" syntaxhighlight.FromString t
				Case "autocapitalize" capitalize.FromString t
				Case "autoindent" autoindent.FromString t
				Case "tabsize" tabsize.set t	
				Case "editcolor" editcolor.FromString b	
				Case "normal_style" importstyle styles[0],b
				Case "comment_style" importstyle styles[1],b
				Case "quote_style" importstyle styles[2],b
				Case "keyword_style" importstyle styles[3],b
'				Case "showtoolbar" showtoolbar=t
'				Case "restoreopenfiles" restoreopenfiles=t
'				Case "autobackup" autobackup=t
'				Case "output_style" outputstyle.FromString(b)
'				Case "nav_style" navstyle.FromString(b)
'				Case "nav_swap" navswap=t
'				Case "hide_output" hideoutput=t
				Case "proj_node" 
'					owner.editor.projlist.AddLast b
'					p=b.find("|")
					'If p<>-1 
					Local projdata:TList
					projdata=New TList
					projdata.AddLast b
					owner.editor.projects.AddProject projdata
'					owner.editor.AddDefaultProj b	'projects.AddProject b	'p'b[..p],b[p+1..]
				Default
'					DebugLog "did not import :"+a$
			End Select
		Wend		
		CloseStream stream
		If editfontname editfont.Set editfontname,editfontsize
	End Method
End Type

Type TBlitzMaxPlugin Extends TPlugin

	Field	editor:TEditor
	Field	options:TBMXOptions
	Field	tokens:TQuickHelp
	Field	nomods
	Field	editmenu:TGadget
	
	Field	syncmodsreq:TSyncModsRequester
	Field	buildmods,buildallmods,syncmods,importbb

	Method Open:Object(path$)
		Return TBMXCode.Open(Self,path)
	End Method
	
	Method BuildModules(buildall)
		Local cmd$,out$,exe$
		editor.output.stop
		editor.SaveAll
		cmd$=quote(bmkpath)
		cmd$:+" makemods "
'		If debugenabled cmd$:+"-d " Else cmd$:+"-r "
		If buildall cmd$:+"-a "
		editor.Execute Null,cmd,"Building Modules"
	End Method
		
	Method Debug$(err$)
		Local p,q,line,column,mess$,file$
		If err$[..13]="Compile Error"
			err=err[14..]
			p=err.find(EOL$)
			If p=-1 p=Len err
			mess=err[..p]
			err=err[p+1..]
			If err[..1]="["
				p=err.find("]")
				If p=-1 p=Len err
				file$=err[1..p]
				p=file.find(";")+1
				If p=0 p=Len err
				q=file.find(";",p)+1
				If q=0 q=Len err
				line=Int(file[p..q-1])
				column=Int(file[q..])
				file=RealPath(file[..p-1])			
				editor.DebugSource file,line,column
				Notify "Compile Error"+EOL+mess
'				editor.DebugExit
				Return
			EndIf
		EndIf
		Return err
	End Method
	
	Method Invoke(command$,argument:Object=Null)
		Select command
			Case "RESET"
				options.ResetPath
				SetHelp
			Case "BUILDMODS" BuildModules False
			Case "BUILDALLMODS" BuildModules True
			Case "SYNCMODS" SyncModsReq.show
			Case "IMPORTBB" ConvertBB
			Default 
				DebugStop
				DebugLog "blitzmaxplugin:invoke command="+command
		End Select
	End Method
	
	Method ConvertBB()
		Local	f$,cmd$,path$
		f$=RequestFile("Import .bb File","bb" )
		If Not f$ Return
		cmd$=quote(bmkpath$)
		cmd$:+" convertbb "
		cmd$:+quote(RealPath(f$))
		editor.Execute Null,cmd,"Converting "+StripExt(StripDir(f$))
'		output.wait
		editor.OpenSource(StripExt(f$)+".bmx")
	End Method

	Method Initialize(owner:THost)
		host=owner
		SetName("BlitzMax",["bmx"])

		BeginSection("&Tools")
		buildmods=AddCommand("&Tools","Build &Modules",KEY_D,MODIFIER_COMMAND)
		buildallmods=AddCommand("&Tools","Rebuild &All Modules")
		syncmods=AddCommand("&Tools","&Synchronize Modules...")
		importbb=AddCommand("&Tools","&BlitzMax Import .bb...")

'		CreateMenu "&Import BB Project...",MENUIMPORTBB,filemenu
		
		editor=TEditor(host)
		If Not editor RuntimeError "BlitzMax plugin Required EditorHost"
		options=New TBMXOptions.CreateBMXOptions(Self)
		editmenu=CreateEditMenu()
		syncmodsreq=TSyncModsRequester.Create(host)	
?win32		
		Local mingw$=getenv_("MINGW")
		If Not mingw
'			DisableMenu buildmods
'			DisableMenu buildallmods
		EndIf
?
		'FindBMXPath(AppDir$)
	End Method

	Function CreateEditMenu:TGadget()
		Local	edit:TGadget
		Const MKEY$=Chr(8)
		edit=CreateMenu("&Edit",0,Null)
		CreateMenu "&Quick Help"+MKEY+"F1",MENUQUICKHELP,edit
		CreateMenu "",0,edit
		CreateMenu "Cu&t"+MKEY+"Ctrl+X",MENUCUT,edit
		CreateMenu "&Copy"+MKEY+"Ctrl+C",MENUCOPY,edit
		CreateMenu "&Paste"+MKEY+"Ctrl+V",MENUPASTE,edit
		CreateMenu "",0,edit
		CreateMenu "Select &All"+MKEY+"Ctrl+A",MENUSELECTALL,edit
		CreateMenu "",0,edit
'		CreateMenu "Block Indent"+MKEY+"Ctrl+TAB",MENUINDENT,edit
'		CreateMenu "Block Outdent"+MKEY+"Shift TAB",MENUOUTDENT,edit
		CreateMenu "Block Indent"+MKEY+"Ctrl+]",MENUINDENT,edit
		CreateMenu "Block Outdent"+MKEY+"Ctrl+[",MENUOUTDENT,edit
		CreateMenu "",0,edit
		CreateMenu "&Find"+MKEY+"Ctrl+F",MENUFIND,edit
		CreateMenu "Find Next"+MKEY+"F3",MENUFINDNEXT,edit
		CreateMenu "&Replace"+MKEY+"Ctrl+H",MENUREPLACE,edit
		CreateMenu "&Goto Line"+MKEY+"Ctrl+G",MENUGOTO,edit
		Return edit
	End Function

	Method SetBMXPath$(path$)	'returns version$ if successul
		Local dir$
		Local version$
		Local stream:TStream
		
		If Not path Or path=bmxpath Return
		
		If FileType(path)=0 Return
		
		bmxpath=path
		bmkpath=bmxpath+"/bin/bmk"
?win32
		bmkpath:+".exe"
?
		dir$=bmxpath+"/mod"
		If FileType(dir)=0
			If Not CreateDir(dir)
				Notify "Failed to Create Module Directory "+dir
				End
			EndIf
			nomods=True
		EndIf
		dir$=bmxpath+"/tmp"
		If FileType(dir)=0
			If Not CreateDir(dir)
				Notify "Failed to Create Temp Directory "+dir
				End
			EndIf
		EndIf
		dir$=bmxpath+"/cfg"
		If FileType(dir)=0
			If Not CreateDir(dir)
				Notify "Failed to Create Config Directory "+dir
				End
			EndIf
			stream=WriteStream(dir+"/modservers.cfg")
			If stream
				stream.WriteLine ("www.blitzbasic.com/modserver2")
				stream.Close
			EndIf
		EndIf
		version$=CheckVersion()
		If Not version Return
		
		ScanBlitzMaxHelp
		tokens=TQuickHelp.LoadCommandsTxt(bmxpath)
		
		putenv_ "BMKPATH"+"="+bmkpath
		
		Return version
	End Method
	
	Method ImportMaxLinks(root:TDataNode,path$)
		Local	file:TStream
		Local	c$,l$,p,q,a$,n$,cat
		Local	node:TDataNode

		file=ReadFile(path)
		If Not file Return
		path=ExtractDir(path)
		node=root
		While Not Eof(file)
			c=ReadLine(file)
			If c="" Continue
			If c.Find("Alphabetic reference")<>-1
				node=host.AddLink(root,"Index","")
				Continue
			EndIf
			l=c.ToLower()
			l=l.Replace(QUOTES$,"'")
			p=l.Find("<a")
			If p=-1 Continue
			a$=""
			q=l.find("href=",p)+1
			If q
				If l[q+4]=39	'"'"
					p=q+5
					q=l.find("'",p)
				Else
					p=q+4
					q=l.find(" ",p)
				EndIf
				If q>p 
					a$=path+"/"+c[p..q]
				EndIf
			EndIf
			p=c.Find(">",q)+1
			If p And c[p]=60 p=c.Find(">",p)+1
			q=c.Find("<",p)
			If q<=p Continue
			n$=c[p..q]
			If l.find("class=null")<>-1
				node=host.AddLink(root,n$,a$)
			Else
				host.AddLink(node,n$,a$)
			EndIf
		Wend
	End Method

	Field	root:TDataNode
	Field user:TDataNode
	Field lang:TDataNode
	Field mods:TDataNode	

	Method ImportLinks(root:TDataNode,path$)
		Local	stream:TStream
		Local	c$,l$,p,q,a$,n$,cat
		Local	node:TDataNode
		
		stream=ReadFile(path)
		If Not stream Return
		path=ExtractDir(path)
		node=root
		While Not Eof(stream)
			c=ReadLine(stream)
			If c="" Continue
			If c.Find("Alphabetic index")<>-1
				node=host.AddLink(root,"Index","")
				Continue
			EndIf
			l=c.ToLower()
			l=l.Replace(QUOTES$,"'")
			p=l.Find("<a")
			If p=-1 Continue
			a$=""
			q=l.find("href=",p)+1
			If q
				If l[q+4]=39	'"'"
					p=q+5
					q=l.find("'",p)
				Else
					p=q+4
					q=l.find(" ",p)
				EndIf
				If q>p 
					a$=path+"/"+c[p..q]
				EndIf
			EndIf
			p=c.Find(">",q)+1
			If p And c[p]=60 p=c.Find(">",p)+1
			q=c.Find("<",p)
			If q<=p Continue
			n$=c[p..q]
			If l.find("onclick=toggle")<>-1
				node=host.AddLink(root,n$,a$)
			Else
				host.AddLink(node,n$,a$)
			EndIf
		Wend
		stream.Close
	End Method

	Method SyncDocs()
		mods.FreeKids
		host.AddLink(mods,"Welcome",bmxpath+"/doc/bmxmods/welcome.html")
		ImportLinks mods,bmxpath+"/doc/bmxmods/navbar.html"
		mods.Refresh
	End Method
	
	Method SetHelp()
		Local html$
		Local docpath$=bmxpath+"/docs/html/"
'		html:+"<div align=center><table width=100% class=open><tr>"
'		html:+"<td class=blank align=left width=33%>&nbsp;</td>"
'		html:+"<td class=blank align=center width=33%><img src="+docpath$+"bmax120.png></td>"
'		html:+"<td class=blank align=right valign=top width=33%><img src="+docpath$+"brllogo.png></td>"
'		html:+"</tr></table>"
'		html:+"</div>Welcome to BlitzMax!<br><ul>"
		html:+"<li><a href='"+docpath$+"User Guide/index.html'>Users guide</a> - how to create programs in BlitzMax.<br>"
		html:+"<li><a href='"+docpath$+"Language/index.html'>Language reference</a> - how the BlitzMax language works.<br>"
		html:+"<li><a href='"+docpath$+"Modules/index.html'>Module reference</a> - commands contained in BlitzMax modules.<br>"
		html:+"<li><a href='"+docpath$+"Tutorials/index.html'>Tutorials and articles</a> - a collection of informal bits And pieces.<br>"
'		html:+"<li><a href='"+bmxpath$+"/samples/index.html'>Sample programs</a> - a selection of short 'n' sweet samples.<br>"
		html:+"<li><a href='"+docpath$+"_credits.html'>Credits</a> - who did what.<br>"
		html:+"</ul>"
		html:+"If you experience any problems running BlitzMax, please contact <a href='mailto:support@blitzbasic.com'>support@blitzbasic.com</a><br><br>"
		html:+"For more information on BlitzMax, visit the Blitz Research website at <a href=http://www.blitzbasic.com>www.blitzbasic.com</a><br><br>"
		html:+"~n"
'		html="<iframe src='file://"+RealPath(bmxpath)+"/doc/index.html'></iframe>"		
		SetProperty("Help",html)
	End Method

	Method ScanBlitzMaxHelp()
'		root=docroot.AddNode("BlitzMax")
		root=host.AddLink(host.docroot,"BlitzMax",bmxpath+"/doc/index.html")
'		user=root.AddNode("UserGuide")
		user=host.AddLink(root,"UserGuide",bmxpath+"/doc/bmxuser/welcome.html")
		ImportMaxLinks user,bmxpath+"/doc/bmxuser/navbar.html"
'		lang=root.AddNode("Language")
		lang=host.AddLink(root,"Language",bmxpath+"/doc/bmxlang/welcome.html")
		ImportMaxLinks lang,bmxpath+"/doc/bmxlang/navbar.html"
'		mods=root.AddNode("Modules")
		mods=host.AddLink(root,"Modules",bmxpath+"/doc/bmxmods/welcome.html")
		SyncDocs
'		host.AddLink(mods,"Welcome",bmxpath+"/doc/bmxmods/welcome.html")
'		mods=AddLink(root,"Modules",host.bmxpath+"/doc/bmxmods/welcome.html")
'		ImportMaxLinks mods,bmxpath+"/doc/bmxmods/navbar.html"
		host.AddLink root,"Samples",bmxpath+"/samples/index.html"
		root.Refresh
'		host.docroot.refresh
	End Method

	Method CheckVersion$()
		Local	process:TProcess
		Local	bytes:Byte[]
		Local	cmd$,version$

		cmd$=bmxpath+"/bin/bcc"
?win32
		cmd:+".exe"
?
		process=CreateProcess(cmd$,HIDECONSOLE)
		If process
			While True
				bytes=process.pipe.ReadPipe()
				If bytes
					version:+String.FromBytes(bytes,Len bytes)
				EndIf
				If Not process.status() Exit				
				Delay 10
			Wend
		Else
'			Notify "CreatProcess failed~ncmd="+cmd
			Return		
		EndIf
		If version=""
			Notify "Unable to locate required files.~n~nPlease modify BlitzMax path in options to repair this problem.~nbmxpath="+bmxpath+"~nversion="+version
			Return
'			End
		EndIf
		If version.find("(expired)")>-1	'Demo timed out
			Notify "The demo version of BlitzMax has expired.~n~nPlease visit www.blitzmax.com to buy the full version."
			Return
'			End
		EndIf
		If version.find("Demo Version")>-1	'Demo timed out
			is_demo=True
			Notify version+"~nTo purchase the full version please visit www.blitzmax.com."
		EndIf
		
		Return version$
			
	End Method
End Type

Type TSyncModsRequester Extends TRequester
	Field	label:TGadget,user:TGadget,password:TGadget,proxy:TGadget

	Method SyncMods()
		Local	u$,p$,x$,cmd$
		u$=quote(TextFieldText(user))
		p$=quote(TextFieldText(password))
		x$=TextFieldText(proxy)
		cmd$=quote(bmkpath)
		cmd$:+" syncmods -u "+u$+" -p "+p$
		If x cmd$:+" -y "+x
		Hide
		host.execute Null,cmd,"Synchronize Modules",MENUDOCMODS,True
	End Method
	
	Method OnEvent()
		Local	id,data
		id=EventID()
		data=EventData()
		
'		If id=EVENT_MENUACTION
'			Select data
'			Case MENUINDENT
'				ActivateGadget window,ACTIVATE_FORWARD
'			End Select
'			Return
'		EndIf
		Select EventSource()
			Case window
				If EventID()=EVENT_WINDOWCLOSE
					Hide
				EndIf
			Case ok
				SyncMods
			Case cancel
				Hide
		End Select
	End Method
	
	Method Show()
		Super.Show()
		ActivateGadget user
	End Method

	Function Create:TSyncModsRequester(host:THost)
		Local	sync:TSyncModsRequester
		Local m$
		
		m$="Synchronize modules will perform an online update of your BlitzMax modules, "
		m:+"automatically installing the latest bug fixes and enhancements. "
		m:+"Please make sure you are online before proceeeding.~n~n"
		m:+"To synchronize modules you must have a valid account at www.blitzmax.com. "
		m:+"If you haven't done so already, visit www.blitzmax.com and sign up to create your account.~n~n"
		m:+"Please enter your account details below. If required you may also enter a proxy server."
		
		sync=New TSyncModsRequester
		sync.initrequester(host,"Synchronize modules",320,400)
		sync.label=CreateLabel(m$,8,20,280,200,sync.window)
		
		CreateLabel("Username",28,244,80,24,sync.window)
		sync.user=CreateTextField(108,240,160,20,sync.window)

		CreateLabel("Password",28,274,80,24,sync.window)
		sync.password=CreateTextField(108,270,160,20,sync.window,1)		

		CreateLabel("Proxy Server",28,304,80,24,sync.window)
		sync.proxy=CreateTextField(108,300,160,20,sync.window)		
		
		Return sync
	End Function
End Type

Type TBMXCode Extends TOpenCode
	
	Field	bmxplugin:TBlitzMaxPlugin
	Field	options:TBMXOptions

	Function Open:TBMXCode(plugin:TBlitzMaxPlugin,path$)
		Local	code:TBMXCode		
		If Not bmxpath Notify "BlitzMax Not Found";Return
		code=New TBMXCode
'		code.isbmx=True
		code.bmxplugin=plugin
		code.host=plugin.host		
		code.options=plugin.options
		code.active=True
		code.path=path
		plugin.host.addpanel(code)		
		code.textarea=CreateTextArea(0,0,ClientWidth(code.panel),ClientHeight(code.panel),code.panel,0)
		SetGadgetFilter code.textarea,code.FilterKey,code
		SetTextAreaText code.textarea,"~n"
		SetGadgetLayout code.textarea,1,1,1,1
		code.Refresh
		code.SetDirty False
		plugin.host.UpdateProgress "Reading Stream"
		code.ReadSource(path)		
		code.UpdateCode
		code.undolist.clear
' cludge first dirty		
		code.filesrc=TextAreaText(code.textarea)
		code.SetDirty False
		Return code
	End Function

	Method OnEvent()
 		Select EventSource()
			Case textarea 
				Select EventID() 
					Case EVENT_GADGETMENU
						PopupWindowMenu host.window,bmxplugin.editmenu
					Case EVENT_GADGETACTION
						UpdateCode
					Case EVENT_GADGETSELECT
						UpdateCursor
				End Select
		End Select
	End Method

	Method Refresh()
		Local	rgb:TColorButton
		Local	src$
		SetTextAreaTabs textarea,options.tabsize.value
		SetTextAreaFont textarea,options.editfont.font
		rgb=options.editcolor
		SetTextAreaColor textarea,rgb.red,rgb.green,rgb.blue,True
		rgb=options.styles[0].color
		SetTextAreaColor textarea,rgb.red,rgb.green,rgb.blue,False
		src=cleansrc
		cleansrc=""
		cleansrcl=""
		cursorpos=0
		SetCode(src)
	End Method

	Method BuildSource(quick,debug,gui,run)		
		Local cmd$,out$,arg$
		cmd$=quote(bmkpath)
		cmd$:+" makeapp"
		If run cmd$:+" -x"
		If debug cmd$:+" -d" Else cmd$:+" -r"	'-v
		If gui cmd$:+" -t gui"
		If Not quick cmd$:+" -a"
		If debug 
			out$=StripExt(host.FullPath(path))+".debug"
			cmd$:+" -o "+quote(out$)+" "			'QUOTES+out$+QUOTES+" "
		EndIf		
		cmd$:+" "+quote(host.FullPath(path))
		If run
			arg$=host.GetCommandLine()
			If arg cmd$:+" "+arg
		EndIf
		host.execute Null,cmd,"Building "+StripExt(StripDir(path)),"",True	',exe$		
	End Method
	
	Method Help()
		Local	p,l$,a$
		If Not bmxplugin.tokens Return
		p=TextAreaCursor(textarea,TEXTAREA_CHARS)
		a$=WordAtPos(cleansrc,p)
		If a$=helpcmd
			l$=bmxplugin.tokens.link(a$)
			If l$ 
				host.Help bmxpath+l$
			EndIf
		Else
			helpcmd=a$
			helpstring$=bmxplugin.tokens.help(a$)
			UpdateStatus	'host.setstatus helpstring$
		EndIf
	End Method

	Method GetNode:TDataNode()
		Local	root:TCodeNode,n:TCodeNode
		Local	src$,line,col
		Local	p,p1,r,t,m,f,l,e

'		if codenode return codenode		
		n=New TCodeNode
		n.name=StripDir(path)
		n.owner=Self
		n.count=src.length
		root=n
'update rem,type,method,function,label pointers
		src=cleansrcl
		p1=src.length
		p=-1;r=-1;t=-1;m=-1;f=-1;l=-1
		While p<p1			'src.length
			While r<=p
				r=FindToken("rem",src,r+1)
			Wend
			While t<=p
				t=FindToken("type",src,t+1)
			Wend
			While m<=p
				m=FindToken("method",src,m+1)
			Wend
			While f<=p
				f=FindToken("function",src,f+1)
			Wend
			While l<=p
				l=FindLabel(src,l+1)
			Wend
			If r<t And r<m And r<f And r<l
				p=FindEndToken("rem",src,r+1,True)
				Continue
			EndIf
			p=Min(t,Min(m,Min(f,l)))
			If p=src.length Exit		
			While (n And n.parent And p>n.pos+n.count) 
				n=TCodeNode(n.parent)
			Wend
			If t<m And t<f And t<l
				e=src.find(EOL,t)
				n=n.AddCodeNode(cleansrc[t..e],t,FindEndToken("type",src,t,True))
				p=t+1
				Continue
			EndIf
			If m<f And m<l
				e=src.find(EOL,m)
				n.AddCodeNode(cleansrc[m..e],m,e)
				p=m+1
				Continue
			EndIf
			If f<l
				e=src.find(EOL,f)
				n.AddCodeNode(cleansrc[f..e],f,e)
				p=f+1
				Continue		
			Else
				e=src.find(EOL,l)
				n.AddCodeNode(cleansrc[l..e],l,e)
				p=l+1
				Continue				
			EndIf
		Wend
		If codenode
			codenode.Sync(root)	
			root.Free
		Else
			codenode=root
		EndIf
		Return codenode
	End Method

	Function IsCode(src$,p)
		Local	l,q,n
		l=src.FindLast(EOL$,src.length-p)
		If l=-1 l=0
		q=src.Find("'",l)
		If q<>-1 And q<p Return
		q=l
		While q<p
			q=src.Find(Chr(34),q)+1
			If q=0 Exit
			If q<=p n:+1
		Wend
		If n&1 Return
		Return True		
	End Function

	Function FindLabel(src$,pos)
		Local	p,q,c
		While pos>=0
			p=src.Find("#",pos)
			If p=-1 Exit
			q=p
			While q>0
				q:-1
				c=src[q]
				If c=13 Return p
				If c=10 Return p
				If c=32 Or c=9 Continue
				Exit
			Wend
			If q=0 Return p
			pos=p+1
		Wend
		Return src.length	
	End Function
	
	Function FindToken(token$,src$,pos)	'lowercase src only!
		Local	p,c,n
		n=token.length
		While pos>=0
			p=src.Find(token,pos)
			If p=-1 Exit
			c=10 If p>0 c=src[p-1]
			If isntalphanumeric(c) 
				If p+n<src.length c=src[p+n]
				If isntalphanumeric(c) 
					If iscode(src,p)
						If p<4 Or src[p-4..p]<>"end " Return p
					EndIf
				EndIf
			EndIf
			pos=p+1
		Wend
		Return src.length
	End Function

	Function FindEndToken(token$,src$,pos,returnlast=False)	'if true returns first character after endtoken
		Local	p,q,e$,n
		
		p=pos
		e$="end"+token
		n=e.length
		While p<src.length
			p=src.Find(e$,p)
			If p=-1 Exit
			If p+n=src.length Or isntalphanumeric(src[p+n])
				If iscode(src,p)
					If p=0 Or isntalphanumeric(src[p-1]) Exit
				EndIf
			EndIf
			p=p+n
		Wend
		If p=-1 p=src.length Else If returnlast p:+n
		q=pos
		e$="end "+token
		n=e.length
		While q<src.length
			q=src.Find(e$,q)
			If q=-1 Exit
			If q+n=src.length Or isntalphanumeric(src[q+n])
				If iscode(src,q)
					If q=0 Or isntalphanumeric(src[q-1]) Exit
				EndIf
			EndIf
			q=q+n
		Wend
		If q=-1 q=src.length Else If returnlast q:+n
		Return Min(p,q)
	End Function

	Function IsFirstCharOnLine(src$,pos)
		Local	i,c
		For i=1 To pos
			c=src[pos-i]
			If c=10 Or c=13 Return True
			If c>32 Return False
		Next
		Return True
	End Function

' rem and endrem must be first nonwhitespace on line - following funcs are for lowercase src only

	Function FindRem(src$,pos)
		While pos<src.length
			pos=FindToken("rem",src,pos)
			If pos=src.length Exit
			If IsFirstCharOnLine(src,pos) Return pos
			pos:+1
		Wend
		Return pos
	End Function

	Function FindEndRem(src$,pos,returnlast=False)
		Local	i,c		
		While pos<src.length
			pos=FindEndToken("rem",src,pos)
			If pos=src.length Exit
			If IsFirstCharOnLine(src,pos)
				If returnlast 
					If src[pos+5]=Asc("e") Or src[pos+5]=Asc("E") pos:+1
					pos:+6
				EndIf
				Return pos
			EndIf
			pos:+1
		Wend
		Return src.length
	End Function

	Function FindPrevRem(src$,pos)	'lowercase src only!
		Local	p,c
		While pos>0
			If pos>src.length Exit
			p=src.FindLast("rem",src.length-pos)
			If p=-1 Exit						
			If isntalphanumeric(src[p+3]) And IsFirstCharOnLine(src,p) Return p			
			pos=p-1
		Wend
		Return -1
	End Function

	Method WasRemmed(pos,src$)
		Local	s$,p
		s$=cleansrcl
		p=src.length-s.length
		If p<0 pos:-p		
		p=FindPrevRem(s$,pos+3)
		If p=-1 Return
		p=FindEndRem(s$,p)
		If pos<p Return True
	End Method

	Method SetCode(src$,diff:TDiff=Null)
		Local	i,p,p1,q,r,a,t$,h$,lsrc$,r0,r1,cpos,autocap
		Local	s:TTextStyle

' update dirty flag	
		CheckDirty src
		If src=cleansrc And Not deferpos And Not diff Return	

		If Not bmxplugin.tokens Or Not options.syntaxhighlight.Selected()	'Not isbmx Or 
			cleansrc=src
			cleansrcl=src.ToLower()
			Return
		EndIf		
' doit
		autocap=options.capitalize.Selected()	'True		'autocapitalize
		lsrc=src.ToLower()
		cpos=TextAreaCursor(textarea,TEXTAREA_CHARS)
		LockTextArea textarea
' calculate highlight region
		If deferpos
			p=deferpos
			p1=p
			deferpos=0
		Else
			If diff
				p=diff.pos
				p1=p+diff.add.length
			Else
				p=firstdiff(src,cleansrc)
				p1=lastdiff(src,cleansrc)
			EndIf
			q=src.length-cleansrc.length
			If p1-p<q p1=p+q
			If p1<p p1=p
		EndIf
' round region to line breaks
		If p>0 p:-1
		If p>src.length p=src.length
		p=src.findlast(EOL,src.length-p)+1
		p1=src.find(EOL,p1)+1
		If p1=0 p1=Len src
' if endrem between p0,p1 and next rem after p1 move p1 forwards
		r1=FindEndRem(lsrc,p)
		If r1<p1 And wasremmed(r1+6,lsrc)
			r0=FindRem(lsrc,r1+6)
			If r0>p1 p1=r0
		EndIf
' if rem between p0,p1 and matching endrem after p1 move p1 forewards 
		r0=FindPrevRem(lsrc,p1)
		If r0>=p And r0+3<>cpos	'defer fix
			r1=FindEndRem(lsrc,r0,True)
			If r1>p1 p1=r1
		EndIf
' if rem before p0 and matching endrem after p0 highlight to endrem and move p0 forwards
		r0=FindPrevRem(lsrc,p)
		If r0<>-1 And r0<p
			r1=FindEndRem(lsrc,r0,True)
			If r1>p
				s=options.styles[COMMENT]
				If r1>p s.Format(textarea,p,r1-p)
				If autocap And r1<src.length
					If lsrc[r1-6..r1]="endrem" And src[r1-6..r1]<>"EndRem" SetTextAreaText textarea,"EndRem",r1-6,6
					If lsrc[r1-7..r1]="end rem" And src[r1-7..r1]<>"End Rem" SetTextAreaText textarea,"End Rem",r1-7,7
				EndIf
				p=r1
			EndIf
		EndIf	
' if was remmed and now isn't move p1 down to nearest rem or endrem
		If WasRemmed(p,lsrc)
			r0=FindRem(lsrc,p)
			r1=FindEndRem(lsrc,r0,True)
			p1=Max(p1,Min(r0,r1))	
		EndIf
' highlight code
		s=options.styles[NORMAL]
		If p1>p s.format(textarea,p,p1-p)
		While p<p1
			host.UpdateProgress("Highlighting Code",(p*100)/p1)
			a=src[p]
' quoted strings
			If a=34
				q=p1
				r=src.find(Chr(34),p+1)
				If r>-1 And r<q q=r+1
				r=src.find(EOL,p+1)
				If r>-1 And r<q q=r
				s=options.styles[QUOTED]
				s.format(textarea,p,q-p)
				p=q
				Continue
			EndIf
' single line comments
			If a=39
				q=p1
				r=src.find(EOL,p+1)
				If r>-1 And r<q q=r
				s=options.styles[COMMENT]
				s.format(textarea,p,q-p)
				p=q
				Continue				
			EndIf
' tokens
			If (a>=65 And a<91) Or (a>=97 And a<123) Or (a=95)
				q=p+1
				While q<p1
					a=src[q]
'					If a<46 Exit
'					If a=47 Exit
'					If a=46 Exit
					If a<48 Exit
					If a>=58 And a<65 Exit
					If a>=91 And a<95 Exit
					If a=96 Exit
					If a>122 Exit
					q:+1
				Wend
				t$=src[p..q]
				h$=bmxplugin.tokens.token(t$)
				If h$
					If h$<>t$ And autocap
						If cpos>q Or cpos<p	'q<>cpos	'simon was here q<>cpos
							SetTextAreaText textarea,h$,p,Len h
						Else
							deferpos=q
						EndIf
					EndIf
					s=options.styles[KEYWORD]
					If h$="Rem" And IsFirstCharOnLine(lsrc,p)	' Not (p>4 And lsrc[p-4..p]="end ")
						If q<>cpos
							q=FindEndRem(lsrc,p,True)
							s=options.styles[COMMENT]
						Else
							deferpos=q
						EndIf						
					EndIf
					s.format(textarea,p,q-p)
				EndIf
				p=q
				Continue
			EndIf
			p:+1
		Wend
		UnlockTextArea textarea
		cleansrc=src
		cleansrcl=lsrc
		CheckDirty src
	End Method
	
'	Method DocMods()
'		quickhelp=TQuickHelp.Create(bmxpath)
'		RefreshAll
'	End Method

End Type
